;;;;=========================================================================
;;;;=========================================================================
;;;;
;;;; This file provides a search and rescue client that wanders randomly.
;;;;
;;;; Specifically, the agent follows these rules:
;;;;    - If the agent is next to a person and isn't carrying anything,
;;;;      and the person is not at an exit, it picks up the person (if there
;;;;      are multiple people, it picks up the first one in the list sent by
;;;;      the server).
;;;;    - If the agent is next to an unoccupied exit and is carrying a
;;;;      person, it drops the person off at the exit.
;;;;    - If neither of the above apply, the agent picks a random square
;;;;      to move into, from the squares directly reachable from its current
;;;;      location (excluding occupied squares).  The agent
;;;;      attempts to avoid moving back to locations that were visited
;;;;      previously.
;;;;    - On the off chance that the agent can't move, it does nothing.
;;;;
;;;; The agent does not check whether its actions are successful.
;;;;
;;;; DO NOT MODIFY THE CONTENTS OF THIS FILE -- it may be overwritten in
;;;; later releases of the Search and Rescue simulator.
;;;;
;;;; Author: Aaron Curtis
;;;;
;;;;=========================================================================
;;;;=========================================================================

(defparameter *memory-size* 8)

(defparameter *has-person* nil)
(defparameter *visited-coordinates* nil)



(defun initialize ()
  (format t "Agent initialization:  done.~%"))





(defun process-result (score)
  (check-type score number "a number")
  (format t "Final Score: ~A~%" score))

;;;; Function:  exit-p
;;;;
;;;; Utility function to tell if a cell is an exit.
;;;;
;;;; Returns: t if the cell is an exit, else nil

(defun exit-p (cell)
    (and (not (null (cell-marker cell)))
         (string-equal
            (cell-marker cell) "EXIT")))

;;;; Function:  visited-p
;;;;
;;;; Utility function to tell if a cell was visited (i.e.
;;;; if mark-visited was called)
;;;;
;;;; Returns: a coordinate structure if the cell was visited,
;;;; otherwise nil

(defun visited-p (cell)
    (find-if
        #'(lambda (x)
            (equalp (coordinates cell) x))
        *visited-coordinates*))

;;;; Function:  mark-visited
;;;;
;;;; Utility function to add some coordinates to the
;;;; agent's memory.

(defun mark-visited (coordinates)
    (setf *visited-coordinates*
        (nconc *visited-coordinates* (list coordinates)))

    ;; Remove the oldest coordinates if the list gets too large
    (if (> (length *visited-coordinates*) *memory-size*)
        (setf *visited-coordinates* (cdr *visited-coordinates*))))


(defun choose-action (perceptions
    &aux (hasValidAction nil) (hasValidDirection nil) (action nil) (direction nil) actionRequest)

    ;; Display the current perceptions
    (format t "~%Perceptions: ~A~%" perceptions)
    (format t "~%Perceived Locations: ~A~%" (list-perceived-coordinates perceptions))
    (format t "~%Perceived Objects: ~A~%" (list-perceived-objects perceptions))

    (let ((nearby-locations     nil)
          (cell-with-person     nil)
          (exit-cell            nil)
          (wander-targets       nil)
          (preferred-targets    nil)
          (action               nil)
          (direction            nil))

        ;; Figure out which cells are directly reachable
        ;; from the current position
        (mapcar
            #'(lambda (cell)
                (if (adjacent-coordinates-p (coordinates cell)
                        (get-current-location perceptions))
                    (setf nearby-locations
                        (nconc nearby-locations (list cell)))))
            (perceptions-longrange_sensors perceptions))

        ;; See if one of the nearby locations contains a person
        (setf cell-with-person
            (find-if
                #'(lambda (cell)
                    (and (not (null (cell-occupant cell)))
                         (string-equal
                            (name (cell-occupant cell))
                            "PERSON")
                         (not (exit-p cell))))
                nearby-locations))

        ;; See if one of the nearby cells is an exit
        (setf exit-cell
            (find-if
                #'(lambda (cell)
                    (and (null (cell-occupant cell))
                         (exit-p cell)))
                nearby-locations))

        ;; See where the agent can wander to from here
        (mapcar
            #'(lambda (cell)
                (if (null (cell-occupant cell))
                    (setf wander-targets
                        (nconc wander-targets (list cell)))))
            nearby-locations)

        ;; See where the agent would prefer to wander from here
        ;; (exclude the previously visited squares)
        (mapcar
            #'(lambda (cell)
                (if (not (visited-p cell))
                    (setf preferred-targets
                        (nconc preferred-targets (list cell)))))
            wander-targets)

        ;; If the last step removed all possible targets, then
        ;; allow non-preferred cells as well
        (if (null preferred-targets)
            (setf preferred-targets wander-targets))

        ;; Now, figure out what to do...
        (cond
            ;; Case 1 - The agent isn't carrying a person, and
            ;; there's a person nearby.
            ((and
                (null *has-person*)
                (not (null cell-with-person)))

                (progn
                    (setf action "PICKUP")
                    (setf direction
                        (get-direction
                            (get-current-location perceptions)
                            (coordinates cell-with-person)))
                    (setf *has-person* t)
                    (setf *visited-coordinates* nil)))

            ;; Case 2 - The agent is carrying a person, and
            ;; there's an exit nearby.
            ((and
                (not (null *has-person*))
                (not (null exit-cell)))

                (progn
                    (setf action "DROPOFF")
                    (setf direction
                        (get-direction
                            (get-current-location perceptions)
                            (coordinates exit-cell)))
                    (setf *has-person* nil)
                    (setf *visited-coordinates* nil)))

            ;; Case 3 - Wander randomly.
            ((not (null preferred-targets))

                (progn
                    (setf action "MOVE")
                    (setf direction
                        (get-direction
                            (get-current-location perceptions)
                            (coordinates
                                (elt preferred-targets
                                    (random (length preferred-targets))))))
                    (mark-visited
                        (get-current-location perceptions))))

            ;; Default - Something is seriously wrong.  Do nothing.
            (t (setf action "NOOP")))

        ;; Print out the chosen action and return it
        (format t "~%Action: ~A ~A~%" action direction)

        (return-from choose-action
            (make-action
                :name action
                :direction direction))))


